/**
 * 
 */
package it.robertobifulco.ias;

import java.util.ArrayList;
import java.util.Iterator;

import it.robertobifulco.ias.principalmanager.*;
import it.robertobifulco.ias.rolemanager.*;

/**
 * @author Roberto Bifulco oltremago@gmail.com
 * 
 */
public class IAS {

	// Per realizzare un singleton
	private static IAS ias;
	// Il gestore dei principal
	private PrincipalManager principalManager;
	// Il gestore dei ruoli
	private RoleManager roleManager;
	// La lista delle operazioni definite
	private OperationList operations;
	// I PermissionHandler registrati all'IAS
	private ArrayList<PermissionHandler> permissionHandlers = new ArrayList<PermissionHandler>(
			1);

	private IAS(PrincipalRepository pRepository, RoleRepository rRepository,
			boolean rRepositoryInRAM, OperationRepository oRepository)
			throws MaximumNumberofOccurenceExceededException {

		this.principalManager = new PrincipalManager(pRepository);
		this.roleManager = new RoleManager(rRepository, rRepositoryInRAM);
		this.operations = OperationList.createOperationList(oRepository);
	}

	public Principal getPrincipal(String id, String password)
			throws ImpossibleToIdentifyPrincipalException {
		return this.principalManager.getPrincipal(id, password);
	}

	public Role getRole(String name) throws RoleUndefinedException {
		return this.roleManager.getRole(name);
	}

	/**
	 * Restituisce una Operation ignorando le regole imposte dai
	 * PermissionHandler
	 * 
	 * @param operationName
	 * @param principal
	 * @return
	 * @throws UnauthorizedOperationException
	 * @throws RoleUndefinedException
	 */
	public Operation getOperationIgnoringHandlers(String operationName,
			Principal principal) throws UnauthorizedOperationException,
			RoleUndefinedException {

		if (principal.isAuthenticated()) {
			String roleName = principal.getRole();
			Role role = roleManager.getRole(roleName);
			String[] roleOperations = role.getOperations();

			for (int i = 0; i < roleOperations.length; ++i) {
				if (roleOperations[i].equals(operationName)) {
					try {
						return operations.getOperation(operationName);
					} catch (NotDefinedOperationException e) {
						throw new UnauthorizedOperationException("Operation \""
								+ operationName
								+ "\" is unauthorized for principal \""
								+ principal.getId()
								+ "\" because it isn't defined!");
					}
				}
			}

			throw new UnauthorizedOperationException("Operation \""
					+ operationName + "\" is unauthorized for principal \""
					+ principal.getId() + "\"");
		} else {
			throw new UnauthorizedOperationException("Principal \""
					+ principal.getId() + "\" is not authenticated");
		}
	}

	public Operation getOperation(String operationName, Principal principal)
			throws UnauthorizedOperationException, RoleUndefinedException {

		Operation operation = getOperationIgnoringHandlers(operationName,
				principal);

		for (Iterator<PermissionHandler> iterator = permissionHandlers
				.iterator(); iterator.hasNext();) {
			iterator.next().verifyPermission(operationName, principal);
		}

		return operation;
	}

	public Operation getOperation(String operationName, Principal principal,
			Object extaInfos) throws UnauthorizedOperationException,
			RoleUndefinedException {
		Operation operation = getOperationIgnoringHandlers(operationName,
				principal);

		for (Iterator<PermissionHandler> iterator = permissionHandlers
				.iterator(); iterator.hasNext();) {
			iterator.next().verifyPermission(operationName, principal,
					extaInfos);
		}

		return operation;
	}

	public OperationList getOperations() {
		return operations;
	}

	public void dispose() {
		operations.dispose();
		operations = null;
		roleManager = null;
		principalManager = null;

		IAS.ias = null;
	}

	/**
	 * Returns the IAS instance if already created, or creates a new one and
	 * returns it.
	 * 
	 * @param pRepository
	 * @param rRepository
	 * @param rRepositoryInRAM
	 * @param oRepository
	 * @return
	 * @throws MaximumNumberofOccurenceExceededException
	 */
	public static IAS createIAS(PrincipalRepository pRepository,
			RoleRepository rRepository, boolean rRepositoryInRAM,
			OperationRepository oRepository)
			throws MaximumNumberofOccurenceExceededException {

		if (ias == null)
			ias = new IAS(pRepository, rRepository, rRepositoryInRAM,
					oRepository);

		return ias;
	}

	/**
	 * Returns an IAS instance or null if the instance is not yet created.
	 * 
	 * @return
	 */
	public static IAS getIAS() {
		return ias;
	}

	/**
	 * Aggiunge un PermissionHandler alla lista dei PermissionHandler. Questi
	 * oggetti vengono interpellati quando viene richiesto un permesso,
	 * valutando parametri aggiuntivi che non sono definiti nel sistema RBAC. In
	 * particolare, e' possibile che la richiesta di un permesso venga fatta
	 * passando informazioni aggiuntive, incapsulate in un oggetto
	 * PermissionExtraInfos. Ogni PermissionHandler puo' modificare l'esito
	 * della richiesta del permesso consultando anche queste informazioni
	 * aggiuntive.
	 * 
	 * @param handler
	 */
	public void addPermissionHandler(PermissionHandler handler) {
		permissionHandlers.add(handler);
	}

	/**
	 * Rimuove un PermissionHandler.
	 * 
	 * @param handler
	 */
	public void removePermissionHandler(PermissionHandler handler) {
		permissionHandlers.remove(handler);
	}
}
